/*
Java board game library.
Copyright (C) 2011  Deepesh Garg

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/

package d.g.boardgames.chess;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import d.g.boardgames.Cell;
import d.g.boardgames.MoveHelper;
import d.g.boardgames.Ray;
import d.g.boardgames.chess.ChessCellState.PieceColor;
import d.g.boardgames.chess.ChessCellState.PieceType;


public class ChessMoveHelper {
	static ArrayList<Ray> kingRayList = new ArrayList<Ray>() ;
	static ArrayList<Ray> kingRayListKingSideCastle = new ArrayList<Ray>() ;
	static ArrayList<Ray> kingRayListQueenSideCastle = new ArrayList<Ray>() ;
	static ArrayList<Ray> kingRayListBothSideCastle = new ArrayList<Ray>() ;
	static ArrayList<Ray> knightRayList = new ArrayList<Ray>() ;
	static ArrayList<Ray> rookRayList = new ArrayList<Ray>();
	static ArrayList<Ray> bishopRayList = new ArrayList<Ray>();
	static ArrayList<Ray> queenRayList = new ArrayList<Ray>();
	
	static ArrayList<Ray> whitePawnFirstMoveList = new ArrayList<Ray>();
	static ArrayList<Ray> whitePawnMoveList = new ArrayList<Ray>();
	static ArrayList<Ray> whitePawnKillList = new ArrayList<Ray>();
	static ArrayList<Ray> blackPawnFirstMoveList = new ArrayList<Ray>();
	static ArrayList<Ray> blackPawnMoveList = new ArrayList<Ray>();
	static ArrayList<Ray> blackPawnKillList = new ArrayList<Ray>();
	
	static {
		kingRayList.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, 0)}))) ;
		kingRayList.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, 1)}))) ;
		kingRayList.add(new Ray(Arrays.asList(new Cell[] {new Cell(0, 1)}))) ;
		kingRayList.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, 1)}))) ;
		kingRayList.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, 0)}))) ;
		kingRayList.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, -1)}))) ;
		kingRayList.add(new Ray(Arrays.asList(new Cell[] {new Cell(0, -1)}))) ;
		kingRayList.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, -1)}))) ;
		
		kingRayListKingSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, 0)}))) ;
		kingRayListKingSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, 1)}))) ;
		kingRayListKingSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(0, 1), new Cell(0, 2)}))) ;
		kingRayListKingSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, 1)}))) ;
		kingRayListKingSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, 0)}))) ;
		kingRayListKingSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, -1)}))) ;
		kingRayListKingSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(0, -1)}))) ;
		kingRayListKingSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, -1)}))) ;
		
		kingRayListQueenSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, 0)}))) ;
		kingRayListQueenSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, 1)}))) ;
		kingRayListQueenSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(0, 1)}))) ;
		kingRayListQueenSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, 1)}))) ;
		kingRayListQueenSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, 0)}))) ;
		kingRayListQueenSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, -1)}))) ;
		kingRayListQueenSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(0, -1), new Cell(0, -2)}))) ;
		kingRayListQueenSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, -1)}))) ;

		kingRayListBothSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, 0)}))) ;
		kingRayListBothSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, 1)}))) ;
		kingRayListBothSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(0, 1), new Cell(0, 2)}))) ;
		kingRayListBothSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, 1)}))) ;
		kingRayListBothSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, 0)}))) ;
		kingRayListBothSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(1, -1)}))) ;
		kingRayListBothSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(0, -1), new Cell(0, -2)}))) ;
		kingRayListBothSideCastle.add(new Ray(Arrays.asList(new Cell[] {new Cell(-1, -1)}))) ;

		knightRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(2,1)}))) ;
		knightRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(2,-1)}))) ;
		knightRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-2,1)}))) ;
		knightRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-2,-1)}))) ;
		knightRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(1,2)}))) ;
		knightRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(1,-2)}))) ;
		knightRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-1,2)}))) ;
		knightRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-1,-2)}))) ;
		
		rookRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(0,1), new Cell(0,2), new Cell(0,3), new Cell(0,4), new Cell(0,5), new Cell(0,6), new Cell(0,7)}))) ;
		rookRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(0,-1), new Cell(0,-2), new Cell(0,-3), new Cell(0,-4), new Cell(0,-5), new Cell(0,-6), new Cell(0,-7)}))) ;
		rookRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-1, 0), new Cell(-2, 0), new Cell(-3, 0), new Cell(-4, 0), new Cell(-5, 0), new Cell(-6, 0), new Cell(-7, 0)}))) ;
		rookRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(1, 0), new Cell(2, 0), new Cell(3, 0), new Cell(4, 0), new Cell(5, 0), new Cell(6, 0), new Cell(7, 0)}))) ;
		
		bishopRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(1,1), new Cell(2,2), new Cell(3,3), new Cell(4,4), new Cell(5,5), new Cell(6,6), new Cell(7,7)}))) ;
		bishopRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(1,-1), new Cell(2,-2), new Cell(3,-3), new Cell(4,-4), new Cell(5,-5), new Cell(6,-6), new Cell(7,-7)}))) ;
		bishopRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-1,1), new Cell(-2,2), new Cell(-3,3), new Cell(-4,4), new Cell(-5,5), new Cell(-6,6), new Cell(-7,7)}))) ;
		bishopRayList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-1,-1), new Cell(-2,-2), new Cell(-3,-3), new Cell(-4,-4), new Cell(-5,-5), new Cell(-6,-6), new Cell(-7,-7)}))) ;
		
		queenRayList.addAll(rookRayList);
		queenRayList.addAll(bishopRayList);
		
		whitePawnFirstMoveList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-1, 0), new Cell(-2, 0)})));
		whitePawnMoveList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-1, 0)})));
		whitePawnKillList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-1, -1)})));
		whitePawnKillList.add(new Ray(Arrays.asList(new Cell[]{new Cell(-1, 1)})));
		blackPawnFirstMoveList.add(new Ray(Arrays.asList(new Cell[]{new Cell(1, 0), new Cell(2, 0)})));
		blackPawnMoveList.add(new Ray(Arrays.asList(new Cell[]{new Cell(1, 0)})));
		blackPawnKillList.add(new Ray(Arrays.asList(new Cell[]{new Cell(1, 1)})));
		blackPawnKillList.add(new Ray(Arrays.asList(new Cell[]{new Cell(1, -1)})));
	}

	static public List<Cell> getPossibleMoves(ChessBoard board, Cell forCell) {
		List<Cell> validMoveList = new ArrayList<Cell>();
		
		ChessCellState cellState = board.getCellState(forCell);
		PieceColor thisCellPieceColor = cellState.pieceColor;
		PieceColor opponentColor = thisCellPieceColor == PieceColor.WHITE? PieceColor.BLACK: PieceColor.WHITE;
		
		List<Cell> updatedKillList = new ArrayList<Cell>();
		List<Ray> moveRayList = getPieceMoveList(board, cellState, forCell);
		List<Ray> killRayList = getPieceKillList(cellState);
		
		List<Cell> killList = MoveHelper.getAllowedNonEmpty(board, killRayList, forCell);
		List<Cell> moveList = MoveHelper.getAllowedEmpty(board, moveRayList, forCell);
		
		for (Cell cell: killList) {
			if (board.getCellState(cell).pieceColor == opponentColor) {
				updatedKillList.add(cell);
			}
		}
		
		if (cellState.pieceType == PieceType.PAWN && board.getPassedPawn() != null) {
			List<Cell> passedPawnKillList = MoveHelper.getAllowedEmpty(board, killRayList, forCell) ;
			if (passedPawnKillList.contains(board.getPassedPawn())) {
				updatedKillList.add(board.getPassedPawn());
			}
		}

		validMoveList.addAll(moveList);
		validMoveList.addAll(updatedKillList);
		return validMoveList;
	}
	
	static public List<Cell> getValidMoves(ChessBoard board, Cell forCell) {
		List<Cell> validMoveList = new ArrayList<Cell>();
		if (board.getCellState(forCell) == null) {
			return validMoveList;
		} 
		if (board.getTurn() != board.getCellState(forCell).pieceColor) {
			return validMoveList;
		}
		ChessCellState fromCellState = board.getCellState(forCell);
		List<Cell> removeCellList = new ArrayList<Cell>();
		validMoveList = getPossibleMoves(board, forCell);
		for (Cell toCell: validMoveList) {
			ChessBoard b = board.clone();
			b.setCellState(forCell, null);
			b.setCellState(toCell, fromCellState);
			if (kingInCheck(b, fromCellState.pieceColor)) {
				removeCellList.add(toCell);
			}
		}
		validMoveList.removeAll(removeCellList);
		if (fromCellState.pieceType == PieceType.KING) {
			Cell queenSideCastelingCell = new Cell(forCell.getRow(), forCell.getCol() - 2) ;
			if (validMoveList.contains(queenSideCastelingCell)) {
				if (board.getCellState(forCell.getRow(), forCell.getCol() - 3) != null) {
					validMoveList.remove(queenSideCastelingCell);
				}
				if (cellInCheck(new Cell(queenSideCastelingCell.getRow(), 3), board, board.getTurn())) {
					validMoveList.remove(queenSideCastelingCell);
				}
			}
			Cell kingSideCastelingCell = new Cell(forCell.getRow(), forCell.getCol() + 2) ;
			if (validMoveList.contains(kingSideCastelingCell)) {
				if (cellInCheck(new Cell(kingSideCastelingCell.getRow(), 5), board, board.getTurn())) {
					validMoveList.remove(kingSideCastelingCell);
				}
			}
		}
		return validMoveList;
	}
	
	static List<Ray> getPieceMoveList(ChessBoard board, ChessCellState cellState, Cell cell) {
		switch (cellState.pieceType) {
			case KING : 
						boolean kingSideCastleAllowed = cellState.pieceColor == PieceColor.WHITE? board.whiteKingSideCastleAllowed: board.blackKingSideCastleAllowed;
						boolean queenSideCastleAllowed = cellState.pieceColor == PieceColor.WHITE? board.whiteQueenSideCastleAllowed: board.blackQueenSideCastleAllowed;
						if (kingSideCastleAllowed && queenSideCastleAllowed) {
							return kingRayListBothSideCastle;
						} else if(kingSideCastleAllowed) {
							return kingRayListKingSideCastle;
						} else if (queenSideCastleAllowed) {
							return kingRayListQueenSideCastle;
						} else {
							return kingRayList;
						}
			case KNIGHT: return knightRayList;
			case ROOK: return rookRayList;
			case BISHOP: return bishopRayList;
			case QUEEN: return queenRayList;
			case PAWN: 
				if (cellState.pieceColor == PieceColor.WHITE) {
					if (cell.getRow() == 6) {
						return whitePawnFirstMoveList;
					} else {
						return whitePawnMoveList;
					}
				} else {
					if (cell.getRow() == 1) {
						return blackPawnFirstMoveList;
					} else {
						return blackPawnMoveList;
					}
				}
		}
		return null;
	}
	
	static List<Ray> getPieceKillList (ChessCellState cellState) {
		switch (cellState.pieceType) {
			case KING : return kingRayList;
			case KNIGHT: return knightRayList;
			case ROOK: return rookRayList;
			case BISHOP: return bishopRayList;
			case QUEEN: return queenRayList;
			case PAWN: 
				if (cellState.pieceColor == PieceColor.WHITE) {
					return whitePawnKillList;
				} else {
					return blackPawnKillList;
				}
		}
		return null;
	}

	static public boolean kingInCheck (ChessBoard board, PieceColor pieceColor) {
		Cell kingCell = null;
		for (int row = 0; row < board.getRowCount(); row++) {
			for (int col = 0; col < board.getColCount(); col++) {
				ChessCellState cellState = board.getCellState(row, col);
				if (cellState != null && cellState.pieceColor == pieceColor && cellState.pieceType == PieceType.KING) {
					kingCell = new Cell(row, col);
					break;
				}
			}
		}
		return cellInCheck(kingCell, board, pieceColor);
	}

	static public boolean cellInCheck (Cell cell, ChessBoard board, PieceColor pieceColor) {
		for (int row = 0; row < board.getRowCount(); row++) {
			for (int col = 0; col < board.getColCount(); col++) {
				ChessCellState cellState = board.getCellState(row, col);
				if (cellState != null && cellState.pieceColor != pieceColor) {
					List<Cell> possibleMovesForOpponentPiece = getPossibleMoves(board, new Cell(row, col));
					if (possibleMovesForOpponentPiece.contains(cell)) {
						return true;
					}					
				}
			}
		}
		return false;
	}

	static public ChessMove getChessMove (ChessBoard board, Cell fromCell, Cell toCell) {
		ChessMove chessMove = new ChessMove();
		ChessCellState chessCellState = board.getCellState(fromCell);
		if (chessCellState.pieceType == PieceType.KING) {
			if ( (fromCell.getCol() - toCell.getCol()) == 2) {
				chessMove.setCasteling(ChessMove.Casteling.QUEEN_SIDE) ;
			}
			if ( (fromCell.getCol() - toCell.getCol()) == -2) {
				chessMove.setCasteling(ChessMove.Casteling.KING_SIDE) ;
			}
		}
		if (chessMove.getCasteling() == ChessMove.Casteling.NONE) {
			chessMove.setFromCell(fromCell);
			chessMove.setToCell(toCell);
		}
		return chessMove;
	}
	
	static public void setFromToCellForCastelingMove(ChessMove chessMove, PieceColor pieceColor) {
		if (chessMove.getCasteling() == ChessMove.Casteling.NONE) {
			return;
		}
		if (pieceColor == PieceColor.WHITE) {
			if (chessMove.getCasteling() == ChessMove.Casteling.KING_SIDE) {
				chessMove.setFromCell(new Cell(7, 4));
				chessMove.setToCell(new Cell(7, 6));
			}
			if (chessMove.getCasteling() == ChessMove.Casteling.QUEEN_SIDE) {
				chessMove.setFromCell(new Cell(7, 4));
				chessMove.setToCell(new Cell(7, 2));
			}
		}
		if (pieceColor == PieceColor.BLACK) {
			if (chessMove.getCasteling() == ChessMove.Casteling.KING_SIDE) {
				chessMove.setFromCell(new Cell(0, 4));
				chessMove.setToCell(new Cell(0, 6));
			}
			if (chessMove.getCasteling() == ChessMove.Casteling.QUEEN_SIDE) {
				chessMove.setFromCell(new Cell(0, 4));
				chessMove.setToCell(new Cell(0, 6));
			}
		}
	}
}
